# 1. 什么是高阶函数和闭包并举例说明。
# 如果A函数作为B函数的参数，B函数在调用完成后，会得到一个结果，则B函数被称为高阶函数，常用高阶函数reduce、sorted、filter、map
# 如：reduce(func,seq)
# 闭包：两个函数嵌套定义，如果在内部函数中访问了外部函数中的变量，并且外函数把内函数返回出来的过程,则会形成一个闭包
def outter(a):
    num = 10
    def inner():
        print(a, num)
    return inner
f = outter(34) #外部函数的传参
f() # 34,10
#
# 2. 请列出functools包内的每个函数的功能作用
# functools包中包含reduce、partial、wraps、lru_cache
from functools import reduce, partial, wraps, lru_cache


# ①reduce:reduce(func,seq，初始值),将序列中的第0个元素和第1个元素传给func函数作为参数计算，返回结果1，
#        接着将结果1和第2个元素再传给func进行运算，返回结果2，
#        接着将结果2和第3个元素再传给func进行运算，返回结果3，一直到所有的元素全部参与运算结束，返回最后结果
def add(x, y):
    return x + y


sum1 = reduce(add, range(10))  # range(10)，得到0-9.将0、1传到add函数加起来得到1，然后将1和第3个元素2再传入add，得到3，接着将3和第4个元素3传入add，得到6，一直到最后
print(sum1)  # 45
# reduce 初始值
s = reduce(lambda x, y: print(x, y), range(1, 5), 100)
print(s)  # 可以看到第一个值时从100开始，并不是从1开始，这就设了个初始值
'''
100 1
None 2
None 3
None 4
None
'''


# ②wraps：当使用装饰器后，包装函数的name、doc属性和被包装函数不同，使用wraps让其保持一致
def outter(wraped):
    @wraps(wraped)
    def inner(*args, **kwargs):
        """装饰器的文档"""
        ret = wraped(*args, **kwargs)
        return ret

    return inner


@outter
def add(x, y):
    """add函数的文档"""
    return x + y


print(add.__name__, add.__doc__)  # 没修改前是使用inner函数装饰器的文档，使用wraps函数前都是装饰器的名字和文档
print(add.__name__, add.__doc__)  # 使用wraps装饰器修改后：add add函数的文档


# ③partial(func, /, *args, **keywords)：偏函数，给func函数固定部分参数值，从而得到一个转换后的函数，可以减少传入那些已经指定的值
# 使用位置参数固定
def add(x, y):
    return x + y


newadd = partial(add, 3)  # 使用偏函数把add函数中的x设置固定值，x=3
print(newadd(4))  # 7
# 使用关键字参数固定,当固定住第一个值时，后续只能使用关键字传参，而且使用关键字传参固定的参数值可以被更改，函数签名和①的不同
newadd = partial(add, x=1)
# print(newadd(2)) # 报错，只能使用关键字传参
print(newadd(y=1))  # 2
print(newadd(x=2, y=1))  # 3，可以重新使用关键字参数传入x的值

# ④lru_cache(maxsize=128, typed=False)：为函数提供缓存功能的装饰器，缓存maxsize传入参数，下一次以相同参数调用时直接返回上一次的结果，节约高开销的函数的调用时间
import time


@lru_cache()
def add(x, y):
    print("-----------")
    time.sleep(2)
    return x + y


print(add(1, 2))  # 第一遍运行过2S出结果
print(add(1, 2))  # 第二遍运行直接调用缓存快速出结果
print(add(1, 2))  # 第三遍也一样
# 注意：如果传参方式变了，那缓存的对象也变了，那就需要重新缓存，结果按流程执行
print(add(1, y=2))  # 等2S再出结果3
print(add(1, y=2))  # 直接调用缓存快速出结果3
# 又变一次：
print(add(x=1, y=2))  # 再等2S再出结果3
print(add(x=1, y=2))  # 直接调用缓存出结果3


# 3. 请使用已学习的装饰器相关知识自己实现functools.lru_cache

def lru_cache():
    cache = {}

    def inner(func):
        def wrapper(*args, **kwargs):
            key = args + tuple(sorted(kwargs.items()))
            if key in cache:
                return cache[key]
            result = func(*args, **kwargs)
            cache[key] = result
            return result

        return wrapper

    return inner


# 4. 什么是类型注解并说明其背后的目的。
# Python是动态语言，变量可以随时被赋值并改变类型，由于不能做任何类型检查，有些问题到运行时才显现出来，并且，函数使用者看到函数时，不知道设计者的意图，可能传错变量的数据类型，通过函数类型注解提醒开发者
#
# 文档字符串：注释做到提醒的作用
def add(x, y):
    """
    就是对函数的注释，解释函数的用途
    :param x:
    :param y:
    :return:
    """
    return x + y


# 函数注解,在变量后面：表明变量的类型，在函数后面跟->int表明返回值的类型
def add(x: int, y: int) -> int:  # 表明x，y为int类型，其返回值应该也是int类型
    return x + y


# 虽然标明了变量的类型，但是仍然不影响传入其他类型的变量，但是会有错误提示
print(add(4, 5))  # 不同的数据类型传入进去时结果不同，跟你设计函数时的想法不同
print(add('a', 'b'))  # 仍然能够传入不同类型的变量，程序能正常运行但有错误提醒Expected type 'int', got 'str' instead

print(add.__annotations__)  # {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}


# 使用函数注解时，开发工具也可以根据你的返回的类型对之后的代码功能进行提示
def sub(x: int, y: int) -> list:
    return x - y


# sub(1,2). # 注解返回值的list类型，使用返回值的方法时，list的各种方法开发工具都会提示出来


# 类型注解：对变量的类型进行注解,也仅仅是做提醒，仍然可以赋值不同的类型
i: int = 3
j: str = 'abc'
y: list = [10]
i = {}  # i重新赋值为字典
print(type(i))  # <class 'dict'> i的类型已经被改成字典了

# 像以上的函数注解、类型注解、文档字符串都仅仅起到了提醒的作用，并不能限制函数、变量的传参类型
#
